#include "Npc.h"
#include "Door.h"
#include "Game.h"
#include "ObjectDatabase.h"
#include "Renderer.h"
#include "StringLib.h"
#include "Util.h"
#include "GameState.h"
#include "Item.h"
#include "Equipment.h"
#include "Player.h"
#include "Tile.h"
#include "Level.h"
#include "Corpse.h"
#include "Potion.h"
#include "RangedWeapon.h"

Npc::Npc(NpcType type, bool hostile) : Character(GT_CH_NPC) {
	m_types[TH_NPC_TYPE] = type;
	m_hostile = hostile;
	const NpcInfo& info = g_objectDatabase->getNpcInfo(type);
	if(info.gender == 3)
		m_gender = GenderType(rand_plus_one(2));
	else
		m_gender = GenderType(info.gender);
	m_maxHealth = m_currentHealth = info.health;
	for(int i = 0; i < NUM_STATS; i++)
		m_stats[i] = info.stats[i];
	m_defaultLevel = info.level;
	m_level = m_defaultLevel + rand(info.levelRange);
	m_experience = levelThresholds[m_level];
	m_humanoid = info.humanoid;
	int levelups = m_level - m_defaultLevel;
	for(int i = 0; i < levelups; i++)
		levelUp(false);
}

Npc::Npc(std::ifstream& file) : Character(GT_CH_NPC, file) {
	int* tokens = Util::intSplit(file);
	m_types[TH_NPC_TYPE] = tokens[0];
	const NpcInfo& info = g_objectDatabase->getNpcInfo(NpcType(tokens[0]));
	m_defaultLevel = info.level;
	m_humanoid = info.humanoid;
	delete[] tokens;
}

Npc::~Npc() {
}

void Npc::save(std::ofstream& file) const {
	file << getType(TH_GENERIC_TYPE) << "\n";
	Character::save(file);
	file << getType(TH_NPC_TYPE) << "\n";
}

void Npc::levelUp(bool verbose) {
	m_level++;
	m_maxHealth = x_plus(m_maxHealth, 10);
	m_currentHealth = m_maxHealth;
	int totalStats = 0;
	for(int i = 0; i < NUM_STATS; i++)
		totalStats += m_stats[i];
	for(int i = 0; i < NUM_STATS; i++)
		if(x_in_y(m_stats[i], totalStats)) m_stats[i]++;
	if(verbose)
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_LEVEL_UP, getColoredName().c_str()));
}

bool Npc::equip(EquipmentLocation location, Item* item, bool takeTurn) {
	if(Character::equip(location, item, takeTurn)) {
		if(takeTurn)
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_EQUIP, getColoredName().c_str(), item->getColoredName().c_str()));
		return true;
	}
	return false;
}

bool Npc::unequip(EquipmentLocation location) {
	if(Character::unequip(location)) {
		Item* item = m_equipment->get(location);
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_UNEQUIP, getColoredName().c_str(), item->getColoredName().c_str()));
		return true;
	}
	return false;
}

void Npc::takeTurn() {
	bool doDefault = false;
	int d = distanceSquared(int(g_player->getTile()->getX()), int(g_player->getTile()->getY()), int(m_tile->getX()), int(m_tile->getY()));
	if(m_hostile && d <= 2) {
		doMeleeAttack(g_player);
	} else if(std::sqrt(float(d)) <= Util::normalize(float(m_stats[PER])/2.0f+std::log(float(m_stats[PER])*3)), 1.0f, 10.0f) {
		std::list<Tile*>* tiles = g_currentLevel->findPath(int(m_tile->getX()), int(m_tile->getY()), int(g_player->getTile()->getX()), int(g_player->getTile()->getY()), this);
		if(tiles->size() > 0) {
			Tile* first = tiles->front();
			if(!move(int(first->getX()-m_tile->getX()), int(first->getY()-m_tile->getY())))
				doDefault = true;
		} else {
			doDefault = true;
		}
		delete tiles;
	} else {
		doDefault = true;
	}
	if(doDefault)
		move(0,0);
}

void Npc::render(float x, float y) {
	g_renderer->render(*this, x, y);
}

std::string Npc::getExperienceLevel() const {
	int levelDifference = m_level - m_defaultLevel;
	if(levelDifference < 0)
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_NEGATIVE);
	else if(levelDifference == 0)
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_NORMAL);
	else if(levelDifference == 1)
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_SLIGHT);
	else if(levelDifference <= 3)
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_MODERATE);
	else if(levelDifference <= 5)
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_HEAVY);
	else
		return g_stringLib->getTextString(ST_NPC_EXPERIENCE_EXTREME);
}

void Npc::logDetails(TextLog& log) const {
	Character::logDetails(log);
	log.addBlankLine();
	if(m_gender == GT_NONE)
		log.addLine(g_stringLib->getTextString(ST_NPC_GENDER_NONE));
	else
		log.addLine(g_stringLib->getTextString(ST_NPC_GENDER, g_stringLib->getMiscString(MST_GENDER, m_gender).c_str()));
	log.addLine(g_stringLib->getTextString(ST_NPC_HEALTH_STATUS, g_stringLib->getMiscString(MST_HEALTH, getHealthStatus()).c_str()));
	log.addLine(g_stringLib->getTextString(ST_NPC_EXPERIENCE_LEVEL, getExperienceLevel().c_str()));
	log.addBlankLine();
	bool hasFirstItem = false;
	for(int i = 0; i < NUM_EQUIPMENT_LOCATIONS; i++) {
		Item* item = m_equipment->get(EquipmentLocation(i));
		if(item != NULL) {
			if(!hasFirstItem) {
				hasFirstItem = true;
				log.addLine(g_stringLib->getTextString(ST_NPC_HAS_ITEM_EQUIPPED, g_stringLib->getMiscString(MST_GENDER, m_gender, 1, true).c_str()));
			}
			log.addLine(item->getName());
		}
	}
}

bool Npc::dealDamage(int damage) {
	bool dead = Character::dealDamage(damage);
	if(getHealthStatus() != HS_NOT_WOUNDED)
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_HEALTH_STATUS, g_stringLib->getMiscString(MST_HEALTH, getHealthStatus()).c_str()));
	if(dead) {
		m_tile->removeCharacter();
		m_tile->getLevel()->removeNpc(this);
		g_gameState->markForDeletion(this);
		if(one_in(2)) {
			Corpse* c = new Corpse(NpcType(getType(TH_NPC_TYPE)));
			m_tile->addItem(c);
			g_gameState->addCorpse(c);
		}
		m_equipment->unequipAll();
		for(std::list<Item*>::iterator iter = m_inventory->begin(); iter != m_inventory->end(); iter++) {
			m_tile->addItem(*iter);
		}
		m_inventory->clear();
	}
	return dead;
}

void Npc::giveExperience(int exp) {
	Character::giveExperience(exp);
	if(m_level == MAX_LEVEL)
		return;
	while(m_experience >= levelThresholds[m_level+1]) {
		levelUp();
	}
}

void Npc::callMeleeAttack(Character* c, MeleeType type, Item* item, bool otherHandEmpty) {
	if(type != MT_OTHER)
		Character::callMeleeAttack(c, type, item, otherHandEmpty);
	else {
		switch(getType(TH_NPC_TYPE)) {
		default:
			meleeAttack(c, rand(m_stats[STR]), (m_stats[NIM]/2)-3, 100);
			break;
		case NT_MUTANT:
		case NT_THRASHING_HORROR:
			meleeAttack(c, rand(m_stats[STR]), (m_stats[NIM]/2)-3, 100, false, GT_ONE, MWP_POISONOUS);
		}
	}
}

void Npc::heal(int amount) {
	Character::heal(amount);
	g_textLog->addLine(g_stringLib->getTextString(ST_NPC_HEAL, getColoredName().c_str()));
}

bool Npc::quaff(Potion* potion) {
	g_textLog->addLine(g_stringLib->getTextString(ST_NPC_QUAFF, getColoredName().c_str(), potion->getColoredName().c_str()));
	return Character::quaff(potion);
}

bool Npc::poison(int damage) {
	g_textLog->addLine(g_stringLib->getTextString(ST_NPC_POISON_DAMAGE, getColoredName().c_str()));
	return Character::poison(damage);
}

void Npc::logCombat(Character* enemy, bool nothing, bool hit, bool blocked, int damage, bool crit, bool flame, bool envy, bool lust) {
	bool player = enemy->getType(TH_GENERIC_TYPE) == GT_CH_PLAYER;
	if(nothing) {
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_NOTHING, getColoredName().c_str()));
	} else if(!hit) {
		if(player)
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_MISS_PLAYER, getColoredName().c_str()));
		else
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_MISS, getColoredName().c_str(), enemy->getColoredName().c_str()));
	} else if(blocked) {
		if(player)
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_BLOCKED_PLAYER, getColoredName().c_str()));
		else
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_BLOCKED, getColoredName().c_str(), enemy->getColoredName().c_str()));
	} else {
		if(crit) {
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_CRIT, getColoredName().c_str()));
		}
		if(flame) {
			if(player)
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_FLAME_PLAYER, getColoredName().c_str()));
			else
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_FLAME, getColoredName().c_str(), enemy->getColoredName().c_str()));
		}
		if(envy) {
			if(player)
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_ENVY_PLAYER, getColoredName().c_str()));
			else
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_ENVY, getColoredName().c_str(), enemy->getColoredName().c_str()));
		} else if(lust) {
			if(player)
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_LUST_PLAYER, getColoredName().c_str()));
			else
				g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_LUST, getColoredName().c_str(), enemy->getColoredName().c_str()));
		}

		if(player)
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_HIT_PLAYER, getColoredName().c_str(), Util::intToString(damage).c_str()));
		else
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_MELEE_HIT, getColoredName().c_str(), enemy->getColoredName().c_str(), Util::intToString(damage).c_str()));
	}
}

bool Npc::addStatusEffect(StatusEffect effect, int power) {
	if(Character::addStatusEffect(effect, power)) {
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_STATUS_EFFECT_COMPOUND, getColoredName().c_str(), g_stringLib->getMiscString(MST_STATUS_EFFECT, effect).c_str()));
		return true;
	} else {
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_STATUS_EFFECT_GAIN, getColoredName().c_str(), g_stringLib->getMiscString(MST_STATUS_EFFECT, effect).c_str()));
		return false;
	}
}

bool Npc::removeStatusEffect(StatusEffect effect) {
	if(Character::removeStatusEffect(effect)) {
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_STATUS_EFFECT_LOSE, getColoredName().c_str(), g_stringLib->getMiscString(MST_STATUS_EFFECT, effect).c_str()));
		return true;
	}
	return false;
}

void Npc::logTrap(TrapType trap, bool hit, int damage) {
	switch(trap) {
	case TT_DAGGER:
		if(hit)
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_TRAP_DAGGER_HIT, getColoredName().c_str(), Util::intToString(damage).c_str()));
		else
			g_textLog->addLine(g_stringLib->getTextString(ST_NPC_TRAP_DAGGER_MISS, getColoredName().c_str()));
		break;
	}
}

bool Npc::openDoor(Door* door) {
	if(m_humanoid && !door->isLocked()) {
		door->open();
		modifyTurnCounter(DEFAULT_TURN_LENGTH/2);
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_OPEN_DOOR, getColoredName().c_str()));
		return true;
	}
	return false;
}

void Npc::logReload(RangedWeapon* weapon, bool noAmmo) const {
	if(!noAmmo)
		g_textLog->addLine(g_stringLib->getTextString(ST_NPC_RELOAD, getColoredName().c_str(), weapon->getColoredItemName().c_str()));
}
